library(data.table)
library(leaflet)
library(RColorBrewer)
library(stringr)
library(geosphere)
library(geojsonR)
library(rgdal)
library(ggmap)
library(dplyr)
library(tidyr)
library(rgeos)
library(shiny)
setwd('/Users/greatyifan/Desktop/@Columbia/2020spring/2_DataViz/course_materials/Exercises/07_fire')

#read in data
df_fire <- fread('building_fires.csv')
df_house <- fread('FDNY_Firehouse_Listing.csv')

1. Location of Severe Fires

# levels(df_fire$HIGHEST_LEVEL_DESC)
# it appears that the levels replicate themselves with with minor description differences, so combine it first.
df_fire[, HIGHEST_LEVEL_DESC := ifelse(HIGHEST_LEVEL_DESC == "11 - First Alarm", "1 - More than initial alarm, less than Signal 7-5" , HIGHEST_LEVEL_DESC)]
df_fire[, HIGHEST_LEVEL_DESC := ifelse(HIGHEST_LEVEL_DESC == "22 - Second Alarm", "2 - 2nd alarm" , HIGHEST_LEVEL_DESC)]
df_fire[, HIGHEST_LEVEL_DESC := ifelse(HIGHEST_LEVEL_DESC == "33 - Third Alarm", "3 - 3rd alarm" , HIGHEST_LEVEL_DESC)]
df_fire[, HIGHEST_LEVEL_DESC := ifelse(HIGHEST_LEVEL_DESC == "44 - Fourth Alarm", "4 - 4th alarm" , HIGHEST_LEVEL_DESC)]
df_fire[, HIGHEST_LEVEL_DESC := ifelse(HIGHEST_LEVEL_DESC == "55 - Fifth Alarm", "5 - 5th alarm" , HIGHEST_LEVEL_DESC)]
df_fire[, HIGHEST_LEVEL_DESC := ifelse(HIGHEST_LEVEL_DESC == "75 - All Hands Working", "7 - Signal 7-5" , HIGHEST_LEVEL_DESC)]

df_fire$HIGHEST_LEVEL_DESC <- factor(df_fire$HIGHEST_LEVEL_DESC)

# cast date-time column into data-time type data
df_fire$ARRIVAL_DATE_TIME <- as.POSIXct(df_fire$ARRIVAL_DATE_TIME, 
                                        format = '%m/%d/%Y %I:%M:%S %p') 
df_fire$INCIDENT_DATE_TIME <- as.POSIXct(df_fire$INCIDENT_DATE_TIME, 
                                         format = '%m/%d/%Y %I:%M:%S %p')

# attribution to mapbox
attr <- "© <a href='https://github.com/ChengweiWang3210'>Chengwei Wang</a>"
# set up a base map
base_map <- leaflet(options = leafletOptions(minZoom = 10, maxZoom = 18)) %>% 
#fix the zoom level so that zoom out of new york too far is not optional.
  addTiles(attribution = attr) %>%
  setView(zoom = 10, lng = -74.00919, lat = 40.69999) %>%
  addProviderTiles(provider = "CartoDB.VoyagerNoLabels")
  
df_highest <- subset(df_fire, df_fire$HIGHEST_LEVEL_DESC == '7 - Signal 7-5')

# add on incident points and popups
base_map %>%
  addCircles(data = df_highest,
             lng = ~lon, lat = ~lat, radius = .1,
             stroke = .5, color = 'red', fillOpacity = .01, 
             popup = paste0('Address: ', df_highest$address, '<br/>', 
                            'Incident Data: ', df_highest$INCIDENT_DATE_TIME, '<br/>',
                            'Total Incident Duration: ',
                            round(df_highest$TOTAL_INCIDENT_DURATION/60),
                            ' minutes'))

NA

2. Layers and Clusters

a) Color by Type of Property

# recategorize property types 

## unique(df_highest$PROPERTY_USE_DESC)[substr(unique(df_highest$PROPERTY_USE_DESC), 1,3 ) < 200]
## unique(df_highest$PROPERTY_USE_DESC)[substr(unique(df_highest$PROPERTY_USE_DESC), 1,3 ) > 200]
list_property <- unique(df_highest$PROPERTY_USE_DESC)
# order of the following codes matters.
list_property[str_detect(tolower(list_property),'store|shop|club|business|cafe|retail|warehouse|sales|service')] <- 'Business Sphere'
list_property[str_detect(tolower(list_property), 'doctor|clinic|hospital|recovery|nursing|care|sanita')] <- 'Medical'
list_property[str_detect(tolower(list_property),'playground|open| 
|street|terminal|lot|bus|pier|outside|yard|processing|recreation|drinking|parking|shed|construction|distribution|aircraft')] <- 'Open Aera'
list_property[str_detect(tolower(list_property), 'family|residential,')] <- 'Residence'
list_property[str_detect(tolower(list_property), 'hotel|dorm|cleaning|storage|shelter|property')] <- 'Dorms, Shelters, Hotels'
list_property[str_detect(tolower(list_property), 'educ|school')] <- 'Schools'
list_property[str_detect(tolower(list_property), 'church|hospices|station|arena|assembly|theater|museum|parlor|office|public|bank|hall|disability|studio|center|court|plant|gym|lab|cleaning|storage|property')] <- 'Public 
'
list_property[str_detect(tolower(list_property), 'undetermined|none')] <- 'Undefined'
# combine recoded property categories with original property_use_desc columns
df_combine <- cbind(unique(df_highest$PROPERTY_USE_DESC), list_property)
colnames(df_combine) <- c('PROPERTY_USE_DESC', 'property')
df_combine <- as.data.frame(df_combine)
# join the recoded property column back to the dataframe
df_fire_property <- left_join(df_fire, df_combine, by = 'PROPERTY_USE_DESC')

# rank the "property" variable's level by the number of incidents falling into these categories
ranked <- sort(table(df_fire_property$property), decreasing = T)
df_fire_property$property <- factor(df_fire_property$property, levels = names(ranked))
## above 2 lines of code are trying to ranking types of property by their frequency, and use this to show a more imformative legend in the following map. 
# brew the colors for the property variable
colors <- brewer.pal(uniqueN(df_fire_property$property), "Set2")
propCol <- colorFactor(colors, df_fire_property$property)

# pick out data with highest level of alarm
df_highest_property <- subset(df_fire_property, 
                              df_fire_property$HIGHEST_LEVEL_DESC == '7 - Signal 7-5')


base_map %>%
  addCircles(data = df_highest_property,
             lng = ~lon, lat = ~lat, radius = 1, color = ~propCol(property),
             weight = 1, stroke = 1, fillOpacity = .7, 
             popup = paste0('Address: ', df_highest_property$address, '<br/>', 
                            'Incident Data: ', df_highest_property$INCIDENT_DATE_TIME, '<br/>',
                            'Total Incident Duration: ',
                            round(df_highest_property$TOTAL_INCIDENT_DURATION/60),
                            ' minutes<br/>',
                            'PropertyType: ', df_highest_property$property)) %>%
  addLegend(data = df_highest_property, group = 'Incidents',
            title = "Property Types", position = "topleft",
            pal = propCol, values = ~property)

b) Cluster

base_map %>%
  addCircleMarkers(data = df_highest_property,
             lng = ~lon, lat = ~lat, radius = .1, color = ~propCol(property),
             stroke = 0, fillOpacity = .9, 
             popup = paste0('Address: ', df_highest_property$address, '<br/>', 
                            'Incident Data: ', df_highest_property$INCIDENT_DATE_TIME, '<br/>',
                            'Total Incident Duration: ',
                            round(df_highest_property$TOTAL_INCIDENT_DURATION/60),
                            ' minutes<br/>',
                            'PropertyType: ', df_highest_property$property), 
             clusterOptions = markerClusterOptions(spiderfyOnMaxZoom = 10)) %>%
  addLegend(data = df_highest_property, group = 'Incidents',
            title = "Property Types", position = "topleft",
            pal = propCol, values = ~property)

3. Fire Houses

# add on an icon png
house_icon <- icons(iconUrl = 
  "/Users/greatyifan/Desktop/@Columbia/2020spring/2_DataViz/assignment/house-icon.png",
                    iconWidth = 8, iconHeight = 8)
base_map %>%
  addCircleMarkers(data = df_highest_property, group = 'Incidents', 
                   lng = ~lon, lat = ~lat, 
                   radius = df_highest_property$UNITS_ONSCENE/4,
                   color = ~propCol(property),
                   stroke = 0, weight = 0, fillOpacity = .7, 
                   popup = ~paste0('Address: ', df_highest_property$address, '<br/>', 
                            'Incident Data: ', df_highest_property$INCIDENT_DATE_TIME, '<br/>',
                            'Total Incident Duration: ',
                            round(df_highest_property$TOTAL_INCIDENT_DURATION/60), 
                            ' minutes<br/>',
                            'PropertyType: ', df_highest_property$property)) %>%
  addLegend(data = df_highest_property, group = 'Incidents',
            pal = propCol, values = ~property, 
            title = 'Property Types', position = 'topleft') %>%
  addMarkers(data = df_house, group = 'Firehouses', 
             lng = ~Longitude, lat = ~Latitude,
             icon = house_icon, 
             popup = ~paste0('Address: ', df_house$FacilityAddress, '<br/>', 
                            'Borough: ', df_house$Borough)) %>%
  addLayersControl(baseGroups = 'openStreetNYC',
                   overlayGroups = c('Incidents','Firehouses'), 
                   options = layersControlOptions(collapsed = T))

4. Distance from Firehouse and Response Time

a) Calculate Distance

# this function returns nrow(x) * nrow(y) matrix, where the i-th column indicates the distances between each geopoint in x with the i-th point in y. Similarly, each element in the j-th row means the distance between the j-th point in x with each point in y. In our case, if we want to find out the nearest firehouse for a certain point, we have to find the minimum element for each row, and return the number of column where the minimum point is in, which is the nearest firehouse for that incident. 

mx_dist <- distm(x = matrix(data = c(df_fire$lon, df_fire$lat), ncol = 2),
      y = matrix(data = c(df_house$Longitude, df_house$Latitude), ncol = 2), 
      fun = distGeo)

min_dist <- apply(mx_dist, 1, min, na.rm = T)

nearest_house <- apply(mx_dist, 1, function(x)which(x == min(x, na.rm = T)))

# nrow(df_house) # we have 218 fire houses

# summary(nearest_house) # everything seems right

df_fire_property$min_dist <- min_dist
df_fire_property$nearest_house <- nearest_house

df_fire_property$diff_time <- df_fire_property$ARRIVAL_DATE_TIME -
  df_fire_property$INCIDENT_DATE_TIME

df_fire_property$diff_time <- as.numeric(df_fire_property$diff_time)
# remove outliers, for more informative graphs

## check for the outliers
head(sort(df_fire_property$min_dist, decreasing  = T)) # one 101812.165 should be removed
[1] 101812.165   4034.679   3543.712   3543.712   3543.712   3543.712
head(sort(df_fire_property$diff_time, decreasing = T)) # two 5339 and 2613 should be removed
[1] 5339 2613 1191 1107 1089 1064
head(sort(df_fire_property$diff_time, decreasing = F)) # one negative number should be removed
[1] -305   12   15   16   17   17
df_fire_property <- df_fire_property[-which(df_fire_property$min_dist > 101812.165),]
df_fire_property <- df_fire_property[-which(df_fire_property$diff_time > 2600),]
df_fire_property <- df_fire_property[-which(df_fire_property$diff_time < 0),]

# minimize the categories of alarms again
df_fire_property$rescale <- ifelse(df_fire_property$HIGHEST_LEVEL_DESC == '1 - More than initial alarm, less than Signal 7-5' | df_fire_property$HIGHEST_LEVEL_DESC =="0 - Initial alarm",
                                   "1", df_fire_property$HIGHEST_LEVEL_DESC)

# set this to factor
df_fire_property$rescale <- factor(df_fire_property$rescale)
levels(df_fire_property$rescale) <- c("less than 2nd alarm", "2nd alarm", "3rd alarm", 
                                      "4th alarm ", "5th alarm", "Signal 7-5")

b) Map of Response Times


pal2 <- brewer.pal(uniqueN(df_fire_property$property), "Paired")
  
residence <- subset(df_fire_property, df_fire_property$property == "Residence")
openA <-  subset(df_fire_property, df_fire_property$property == "Open 
                 ")
dorm <- subset(df_fire_property, df_fire_property$property == "Dorms, Shelters, Hotels")
public <- subset(df_fire_property, df_fire_property$property == "Public 
                 ")
school <- subset(df_fire_property, df_fire_property$property == "Schools")
medical <- subset(df_fire_property, df_fire_property$property == "Medical")


base_map %>%
  addCircleMarkers(data = residence, 
                   group = 'Residence', 
                   lng = ~lon, lat = ~lat, 
                   color = pal2[1],
                   radius = residence$diff_time/100,
                   stroke = 0, weight = 0, fillOpacity = .7, 
                   popup = ~paste0('Address: ', residence$address, '<br/>', 
                            'Incident Data: ', residence$INCIDENT_DATE_TIME, '<br/>',
                            'Total Incident Duration: ',
                            round(residence$TOTAL_INCIDENT_DURATION/60), ' min')) %>% 
  addCircleMarkers(data = openA, 
                   group = 'Open 
                   ', 
                   lng = ~lon, lat = ~lat, 
                   color = pal2[2],
                   radius = openA$diff_time/100,
                   stroke = 0, weight = 0, fillOpacity = .7, 
                   popup = ~paste0('Address: ', openA$address, '<br/>', 
                            'Incident Data: ', openA$INCIDENT_DATE_TIME, '<br/>',
                            'Total Incident Duration: ',
                            round(openA$TOTAL_INCIDENT_DURATION/60), ' min')) %>% 
    addCircleMarkers(data = dorm, 
                   group = 'Dorms, Shelters, Hotels', 
                   lng = ~lon, lat = ~lat, 
                   color = pal2[3],
                   radius = dorm$diff_time/100,
                   stroke = 0, weight = 0, fillOpacity = .7, 
                   popup = ~paste0('Address: ', dorm$address, '<br/>', 
                            'Incident Data: ', dorm$INCIDENT_DATE_TIME, '<br/>',
                            'Total Incident Duration: ',
                            round(dorm$TOTAL_INCIDENT_DURATION/60), ' min')) %>% 
  addCircleMarkers(data = public, 
                   group = 'Public 
                   ', 
                   lng = ~lon, lat = ~lat, 
                   color = pal2[4],
                   radius = public$diff_time/100,
                   stroke = 0, weight = 0, fillOpacity = .7, 
                   popup = ~paste0('Address: ', public$address, '<br/>', 
                            'Incident Data: ', public$INCIDENT_DATE_TIME, '<br/>',
                            'Total Incident Duration: ',
                            round(public$TOTAL_INCIDENT_DURATION/60), ' min')) %>% 
  addCircleMarkers(data = school, 
                   group = 'Schools', 
                   lng = ~lon, lat = ~lat, 
                   color = pal2[5],
                   radius = school$diff_time/100,
                   stroke = 0, weight = 0, fillOpacity = .7, 
                   popup = ~paste0('Address: ', school$address, '<br/>', 
                            'Incident Data: ', school$INCIDENT_DATE_TIME, '<br/>',
                            'Total Incident Duration: ',
                            round(school$TOTAL_INCIDENT_DURATION/60), ' min')) %>% 
  addCircleMarkers(data = medical, 
                   group = 'Medical', 
                   lng = ~lon, lat = ~lat, 
                   color = pal2[6],
                   radius = medical$diff_time/100,
                   stroke = 0, weight = 0, fillOpacity = .7, 
                   popup = ~paste0('Address: ', medical$address, '<br/>', 
                            'Incident Data: ', medical$INCIDENT_DATE_TIME, '<br/>',
                            'Total Incident Duration: ',
                            round(medical$TOTAL_INCIDENT_DURATION/60), ' min')) %>% 
  addLayersControl(overlayGroups = c('Residence','Open 
                                     ',
                                     'Dorms, Shelters, Hotels', 'Public 
                                     ',
                                     'Medical','Schools'),
                   options = layersControlOptions(collapsed = F), 
                   position = "topleft") 

NA

It is really hard to compare the response time among various types of property only by color and size, so I use the checkbox feature in the layers control to make it easier for readers to compare by checking different kinds of property.

levels(df_fire_property$HIGHEST_LEVEL_DESC)[2] <- "1 - More than initial alarm"

pal2 <- brewer.pal(uniqueN(df_fire_property[!df_fire_property$HIGHEST_LEVEL_DESC %in% NA, ]$HIGHEST_LEVEL_DESC), 
                   'YlOrRd')
propCol2 <- colorFactor(palette = pal2, domain = df_fire_property$HIGHEST_LEVEL_DESC)

base_map %>%
  addCircleMarkers(data = df_fire_property[!df_fire_property$HIGHEST_LEVEL_DESC %in% NA, ], 
                   group = 'Incidents', 
                   lng = ~lon, lat = ~lat, 
                   radius = df_fire_property$diff_time/100,
                   color = ~propCol2(HIGHEST_LEVEL_DESC),
                   stroke = 0, weight = 0, fillOpacity = .7, 
                   popup = ~paste0('Address: ', df_fire_property$address, '<br/>', 
                            'Incident Data: ', df_fire_property$INCIDENT_DATE_TIME, '<br/>',
                            'Total Incident Duration: ',
                            round(df_fire_property$TOTAL_INCIDENT_DURATION/60), ' min<br/>',
                            'PropertyType: ', df_fire_property$property)) %>%
  addLegend(data = df_fire_property[!df_fire_property$HIGHEST_LEVEL_DESC %in% NA, ], 
            group = 'Incidents',
            pal = propCol2, values = ~HIGHEST_LEVEL_DESC, 
            title = 'Level of Alarms', position = 'topleft')

NA
nyc <- readOGR('/Users/greatyifan/Desktop/@Columbia/2020spring/2_DataViz/course_materials/Exercises/07_fire/borough_boundaries.geojson')
OGR data source with driver: GeoJSON 
Source: "/Users/greatyifan/Desktop/@Columbia/2020spring/2_DataViz/course_materials/Exercises/07_fire/borough_boundaries.geojson", layer: "borough_boundaries"
with 5 features
It has 4 fields
df_fire_property$year <- year(df_fire_property$ARRIVAL_DATE_TIME)

df_fire_property$boro_name <- gsub('\\d{1}\\s-\\s', '', df_fire_property$BOROUGH_DESC)
df_fire_property$year <- paste0("n_", df_fire_property$year) #avoiding valuable names is number

boro_year_count <- df_fire_property %>% 
  group_by(year, boro_name) %>% 
  count() %>% 
  drop_na() %>% 
  spread(key = 'year', value = 'n')
nyc@data <- nyc@data %>% 
  left_join(boro_year_count, on = "boro_name")
# get centroid for each borough
centers <- as.data.frame(gCentroid(nyc, byid = T))
pal_year <- colorBin("YlOrRd", bins = seq(from = 0, to = 900, by = 100))

map_2013 <- base_map %>% 
  addPolygons(data = nyc, stroke = FALSE, smoothFactor = 0.5,
              weight=1, color='#333333', opacity=1,
              fillColor = ~pal_year(n_2013),
              fillOpacity = .8) %>% 
  addLegend(data = nyc@data, pal = pal_year, position = "topleft", 
            values = ~n_2013, title = "<font size='5'>2013</font>") %>% 
  addLabelOnlyMarkers(lng = centers[,1], lat = centers[,2], 
                      label = paste0(nyc@data$boro_name, "\n", nyc@data$n_2013),
                      labelOptions = labelOptions(noHide = T, textOnly = T, 
                                                  direction = 'center'))

map_2014 <- base_map %>% 
  addPolygons(data = nyc, stroke = FALSE, smoothFactor = 0.5,
              weight=1, color='#333333', opacity=1,
              fillColor = ~pal_year(n_2014),
              fillOpacity = .8) %>% 
  addLegend(data = nyc@data, pal = pal_year, position = "topleft", 
            values = ~n_2014, title = "<font size='5'>2014</font>") %>% 
  addLabelOnlyMarkers(lng = centers[,1], lat = centers[,2], 
                      label = paste0(nyc@data$boro_name, "\n", nyc@data$n_2014),
                      labelOptions = labelOptions(noHide = T, textOnly = T, 
                                                  direction = 'center'))

map_2015 <- base_map %>% 
  addPolygons(data = nyc, stroke = FALSE, smoothFactor = 0.5,
              weight=1, color='#333333', opacity=1,
              fillColor = ~pal_year(n_2015),
              fillOpacity = .8) %>% 
  addLegend(data = nyc@data, pal = pal_year, position = "topleft", 
            values = ~n_2015, title = "<font size='5'>2015</font>") %>% 
  addLabelOnlyMarkers(lng = centers[,1], lat = centers[,2], 
                      label = paste0(nyc@data$boro_name, "\n", nyc@data$n_2015),
                      labelOptions = labelOptions(noHide = T, textOnly = T, 
                                                  direction = 'center'))

map_2016 <- base_map %>% 
  addPolygons(data = nyc, stroke = FALSE, smoothFactor = 0.5,
              weight=1, color='#333333', opacity=1,
              fillColor = ~pal_year(n_2016),
              fillOpacity = .8) %>% 
  addLegend(data = nyc@data, pal = pal_year, position = "topleft", 
            values = ~n_2016, title = "<font size='5'>2016</font>") %>% 
  addLabelOnlyMarkers(lng = centers[,1], lat = centers[,2], 
                      label = paste0(nyc@data$boro_name, "\n", nyc@data$n_2016),
                      labelOptions = labelOptions(noHide = T, textOnly = T, 
                                                  direction = 'center'))

map_2017 <- base_map %>% 
  addPolygons(data = nyc, stroke = FALSE, smoothFactor = 0.5,
              weight=1, color='#333333', opacity=1,
              fillColor = ~pal_year(n_2017),
              fillOpacity = .8) %>% 
  addLegend(data = nyc@data, pal = pal_year, position = "topleft", 
            values = ~n_2017, title = "<font size='5'>2017</font>") %>% 
  addLabelOnlyMarkers(lng = centers[,1], lat = centers[,2], 
                      label = paste0(nyc@data$boro_name, "\n", nyc@data$n_2017),
                      labelOptions = labelOptions(noHide = T, textOnly = T, 
                                                  direction = 'center'))

map_2018 <- base_map %>% 
  addPolygons(data = nyc, stroke = FALSE, smoothFactor = 0.5,
              weight=1, color='#333333', opacity=1,
              fillColor = ~pal_year(n_2018),
              fillOpacity = .8) %>% 
  addLegend(data = nyc@data, pal = pal_year, position = "topleft", 
            values = ~n_2018, title = "<font size='5'>2018</font>") %>% 
  addLabelOnlyMarkers(lng = centers[,1], lat = centers[,2], 
                      label = paste0(nyc@data$boro_name, "\n", nyc@data$n_2018),
                      labelOptions = labelOptions(noHide = T, textOnly = T, 
                                                  direction = 'center'))
leaflet_grid <- 
  tagList(
    tags$table(width = "100%",
               tags$tr(
                 tags$td(map_2013),
                 tags$td(map_2014)
               ),
               tags$tr(
                 tags$td(map_2015),
                 tags$td(map_2016)
               ),
               tags$tr(
                 tags$td(map_2017),
                 tags$td(map_2018)
               )
    )
  )

browsable(leaflet_grid)
Bronx 636
Staten Island 177
Brooklyn 831
Queens 661
Manhattan 545
2013
0 – 100
100 – 200
200 – 300
300 – 400
400 – 500
500 – 600
600 – 700
700 – 800
800 – 900
Leaflet | © Chengwei Wang, © OpenStreetMap contributors © CARTO
Bronx 561
Staten Island 156
Brooklyn 821
Queens 650
Manhattan 561
2014
0 – 100
100 – 200
200 – 300
300 – 400
400 – 500
500 – 600
600 – 700
700 – 800
800 – 900
Leaflet | © Chengwei Wang, © OpenStreetMap contributors © CARTO
Bronx 536
Staten Island 162
Brooklyn 814
Queens 664
Manhattan 538
2015
0 – 100
100 – 200
200 – 300
300 – 400
400 – 500
500 – 600
600 – 700
700 – 800
800 – 900
Leaflet | © Chengwei Wang, © OpenStreetMap contributors © CARTO
Bronx 466
Staten Island 147
Brooklyn 734
Queens 627
Manhattan 479
2016
0 – 100
100 – 200
200 – 300
300 – 400
400 – 500
500 – 600
600 – 700
700 – 800
800 – 900
Leaflet | © Chengwei Wang, © OpenStreetMap contributors © CARTO
Bronx 474
Staten Island 119
Brooklyn 727
Queens 551
Manhattan 426
2017
0 – 100
100 – 200
200 – 300
300 – 400
400 – 500
500 – 600
600 – 700
700 – 800
800 – 900
Leaflet | © Chengwei Wang, © OpenStreetMap contributors © CARTO
Bronx 255
Staten Island 61
Brooklyn 323
Queens 254
Manhattan 219
2018
0 – 100
100 – 200
200 – 300
300 – 400
400 – 500
500 – 600
600 – 700
700 – 800
800 – 900
Leaflet | © Chengwei Wang, © OpenStreetMap contributors © CARTO
LS0tCnRpdGxlOiAiQXNzaWdubWVudCAyIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgo8c3R5bGU+Ci5sZWFmbGV0IHsKICAgIG1hcmdpbjogYXV0bzsKfQo8L3N0eWxlPgoKYGBge3IgbGlicmFyaWVzLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkobGVhZmxldCkKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeShnZW9zcGhlcmUpCmxpYnJhcnkoZ2VvanNvblIpCmxpYnJhcnkocmdkYWwpCmxpYnJhcnkoZ2dtYXApCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkocmdlb3MpCmxpYnJhcnkoc2hpbnkpCmBgYAoKYGBge3IgcmVhZCBkYXRhLCB3YXJuaW5nPUZBTFNFfQpzZXR3ZCgnL1VzZXJzL2dyZWF0eWlmYW4vRGVza3RvcC9AQ29sdW1iaWEvMjAyMHNwcmluZy8yX0RhdGFWaXovY291cnNlX21hdGVyaWFscy9FeGVyY2lzZXMvMDdfZmlyZScpCgojcmVhZCBpbiBkYXRhCmRmX2ZpcmUgPC0gZnJlYWQoJ2J1aWxkaW5nX2ZpcmVzLmNzdicpCmRmX2hvdXNlIDwtIGZyZWFkKCdGRE5ZX0ZpcmVob3VzZV9MaXN0aW5nLmNzdicpCmBgYAoKCiMjIDEuIExvY2F0aW9uIG9mIFNldmVyZSBGaXJlcwoKYGBge3IgZGF0YSBjbGVhbmluZ30KIyBsZXZlbHMoZGZfZmlyZSRISUdIRVNUX0xFVkVMX0RFU0MpCiMgaXQgYXBwZWFycyB0aGF0IHRoZSBsZXZlbHMgcmVwbGljYXRlIHRoZW1zZWx2ZXMgd2l0aCB3aXRoIG1pbm9yIGRlc2NyaXB0aW9uIGRpZmZlcmVuY2VzLCBzbyBjb21iaW5lIGl0IGZpcnN0LgpkZl9maXJlWywgSElHSEVTVF9MRVZFTF9ERVNDIDo9IGlmZWxzZShISUdIRVNUX0xFVkVMX0RFU0MgPT0gIjExIC0gRmlyc3QgQWxhcm0iLCAiMSAtIE1vcmUgdGhhbiBpbml0aWFsIGFsYXJtLCBsZXNzIHRoYW4gU2lnbmFsIDctNSIgLCBISUdIRVNUX0xFVkVMX0RFU0MpXQpkZl9maXJlWywgSElHSEVTVF9MRVZFTF9ERVNDIDo9IGlmZWxzZShISUdIRVNUX0xFVkVMX0RFU0MgPT0gIjIyIC0gU2Vjb25kIEFsYXJtIiwgIjIgLSAybmQgYWxhcm0iICwgSElHSEVTVF9MRVZFTF9ERVNDKV0KZGZfZmlyZVssIEhJR0hFU1RfTEVWRUxfREVTQyA6PSBpZmVsc2UoSElHSEVTVF9MRVZFTF9ERVNDID09ICIzMyAtIFRoaXJkIEFsYXJtIiwgIjMgLSAzcmQgYWxhcm0iICwgSElHSEVTVF9MRVZFTF9ERVNDKV0KZGZfZmlyZVssIEhJR0hFU1RfTEVWRUxfREVTQyA6PSBpZmVsc2UoSElHSEVTVF9MRVZFTF9ERVNDID09ICI0NCAtIEZvdXJ0aCBBbGFybSIsICI0IC0gNHRoIGFsYXJtIiAsIEhJR0hFU1RfTEVWRUxfREVTQyldCmRmX2ZpcmVbLCBISUdIRVNUX0xFVkVMX0RFU0MgOj0gaWZlbHNlKEhJR0hFU1RfTEVWRUxfREVTQyA9PSAiNTUgLSBGaWZ0aCBBbGFybSIsICI1IC0gNXRoIGFsYXJtIiAsIEhJR0hFU1RfTEVWRUxfREVTQyldCmRmX2ZpcmVbLCBISUdIRVNUX0xFVkVMX0RFU0MgOj0gaWZlbHNlKEhJR0hFU1RfTEVWRUxfREVTQyA9PSAiNzUgLSBBbGwgSGFuZHMgV29ya2luZyIsICI3IC0gU2lnbmFsIDctNSIgLCBISUdIRVNUX0xFVkVMX0RFU0MpXQoKZGZfZmlyZSRISUdIRVNUX0xFVkVMX0RFU0MgPC0gZmFjdG9yKGRmX2ZpcmUkSElHSEVTVF9MRVZFTF9ERVNDKQoKIyBjYXN0IGRhdGUtdGltZSBjb2x1bW4gaW50byBkYXRhLXRpbWUgdHlwZSBkYXRhCmRmX2ZpcmUkQVJSSVZBTF9EQVRFX1RJTUUgPC0gYXMuUE9TSVhjdChkZl9maXJlJEFSUklWQUxfREFURV9USU1FLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvcm1hdCA9ICclbS8lZC8lWSAlSTolTTolUyAlcCcpIApkZl9maXJlJElOQ0lERU5UX0RBVEVfVElNRSA8LSBhcy5QT1NJWGN0KGRmX2ZpcmUkSU5DSURFTlRfREFURV9USU1FLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXQgPSAnJW0vJWQvJVkgJUk6JU06JVMgJXAnKQoKIyBhdHRyaWJ1dGlvbiB0byBtYXBib3gKYXR0ciA8LSAiwqkgPGEgaHJlZj0naHR0cHM6Ly9naXRodWIuY29tL0NoZW5nd2VpV2FuZzMyMTAnPkNoZW5nd2VpIFdhbmc8L2E+IgoKYGBgCgpgYGB7ciBRMSwgY2FjaGU9RkFMU0UsIGZpZy5hbGlnbj0iY2VudGVyIn0KIyBzZXQgdXAgYSBiYXNlIG1hcApiYXNlX21hcCA8LSBsZWFmbGV0KG9wdGlvbnMgPSBsZWFmbGV0T3B0aW9ucyhtaW5ab29tID0gMTAsIG1heFpvb20gPSAxOCkpICU+JSAKI2ZpeCB0aGUgem9vbSBsZXZlbCBzbyB0aGF0IHpvb20gb3V0IG9mIG5ldyB5b3JrIHRvbyBmYXIgaXMgbm90IG9wdGlvbmFsLgogIGFkZFRpbGVzKGF0dHJpYnV0aW9uID0gYXR0cikgJT4lCiAgc2V0Vmlldyh6b29tID0gMTAsIGxuZyA9IC03NC4wMDkxOSwgbGF0ID0gNDAuNjk5OTkpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXIgPSAiQ2FydG9EQi5Wb3lhZ2VyTm9MYWJlbHMiKQogIApkZl9oaWdoZXN0IDwtIHN1YnNldChkZl9maXJlLCBkZl9maXJlJEhJR0hFU1RfTEVWRUxfREVTQyA9PSAnNyAtIFNpZ25hbCA3LTUnKQoKIyBhZGQgb24gaW5jaWRlbnQgcG9pbnRzIGFuZCBwb3B1cHMKYmFzZV9tYXAgJT4lCiAgYWRkQ2lyY2xlcyhkYXRhID0gZGZfaGlnaGVzdCwKICAgICAgICAgICAgIGxuZyA9IH5sb24sIGxhdCA9IH5sYXQsIHJhZGl1cyA9IC4xLAogICAgICAgICAgICAgc3Ryb2tlID0gLjUsIGNvbG9yID0gJ3JlZCcsIGZpbGxPcGFjaXR5ID0gLjAxLCAKICAgICAgICAgICAgIHBvcHVwID0gcGFzdGUwKCdBZGRyZXNzOiAnLCBkZl9oaWdoZXN0JGFkZHJlc3MsICc8YnIvPicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0luY2lkZW50IERhdGE6ICcsIGRmX2hpZ2hlc3QkSU5DSURFTlRfREFURV9USU1FLCAnPGJyLz4nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1RvdGFsIEluY2lkZW50IER1cmF0aW9uOiAnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoZGZfaGlnaGVzdCRUT1RBTF9JTkNJREVOVF9EVVJBVElPTi82MCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnIG1pbnV0ZXMnKSkKCmBgYAoKCiMjIDIuIExheWVycyBhbmQgQ2x1c3RlcnMKCiMjIyBhKSBDb2xvciBieSBUeXBlIG9mIFByb3BlcnR5CgpgYGB7ciBkZWFsaW5nIHByb3BlcnR5fQojIHJlY2F0ZWdvcml6ZSBwcm9wZXJ0eSB0eXBlcyAKCiMjIHVuaXF1ZShkZl9oaWdoZXN0JFBST1BFUlRZX1VTRV9ERVNDKVtzdWJzdHIodW5pcXVlKGRmX2hpZ2hlc3QkUFJPUEVSVFlfVVNFX0RFU0MpLCAxLDMgKSA8IDIwMF0KIyMgdW5pcXVlKGRmX2hpZ2hlc3QkUFJPUEVSVFlfVVNFX0RFU0MpW3N1YnN0cih1bmlxdWUoZGZfaGlnaGVzdCRQUk9QRVJUWV9VU0VfREVTQyksIDEsMyApID4gMjAwXQpsaXN0X3Byb3BlcnR5IDwtIHVuaXF1ZShkZl9oaWdoZXN0JFBST1BFUlRZX1VTRV9ERVNDKQojIG9yZGVyIG9mIHRoZSBmb2xsb3dpbmcgY29kZXMgbWF0dGVycy4KbGlzdF9wcm9wZXJ0eVtzdHJfZGV0ZWN0KHRvbG93ZXIobGlzdF9wcm9wZXJ0eSksJ3N0b3JlfHNob3B8Y2x1YnxidXNpbmVzc3xjYWZlfHJldGFpbHx3YXJlaG91c2V8c2FsZXN8c2VydmljZScpXSA8LSAnQnVzaW5lc3MgU3BoZXJlJwpsaXN0X3Byb3BlcnR5W3N0cl9kZXRlY3QodG9sb3dlcihsaXN0X3Byb3BlcnR5KSwgJ2RvY3RvcnxjbGluaWN8aG9zcGl0YWx8cmVjb3Zlcnl8bnVyc2luZ3xjYXJlfHNhbml0YScpXSA8LSAnTWVkaWNhbCcKbGlzdF9wcm9wZXJ0eVtzdHJfZGV0ZWN0KHRvbG93ZXIobGlzdF9wcm9wZXJ0eSksJ3BsYXlncm91bmR8b3BlbnwgCnxzdHJlZXR8dGVybWluYWx8bG90fGJ1c3xwaWVyfG91dHNpZGV8eWFyZHxwcm9jZXNzaW5nfHJlY3JlYXRpb258ZHJpbmtpbmd8cGFya2luZ3xzaGVkfGNvbnN0cnVjdGlvbnxkaXN0cmlidXRpb258YWlyY3JhZnQnKV0gPC0gJ09wZW4gQWVyYScKbGlzdF9wcm9wZXJ0eVtzdHJfZGV0ZWN0KHRvbG93ZXIobGlzdF9wcm9wZXJ0eSksICdmYW1pbHl8cmVzaWRlbnRpYWwsJyldIDwtICdSZXNpZGVuY2UnCmxpc3RfcHJvcGVydHlbc3RyX2RldGVjdCh0b2xvd2VyKGxpc3RfcHJvcGVydHkpLCAnaG90ZWx8ZG9ybXxjbGVhbmluZ3xzdG9yYWdlfHNoZWx0ZXJ8cHJvcGVydHknKV0gPC0gJ0Rvcm1zLCBTaGVsdGVycywgSG90ZWxzJwpsaXN0X3Byb3BlcnR5W3N0cl9kZXRlY3QodG9sb3dlcihsaXN0X3Byb3BlcnR5KSwgJ2VkdWN8c2Nob29sJyldIDwtICdTY2hvb2xzJwpsaXN0X3Byb3BlcnR5W3N0cl9kZXRlY3QodG9sb3dlcihsaXN0X3Byb3BlcnR5KSwgJ2NodXJjaHxob3NwaWNlc3xzdGF0aW9ufGFyZW5hfGFzc2VtYmx5fHRoZWF0ZXJ8bXVzZXVtfHBhcmxvcnxvZmZpY2V8cHVibGljfGJhbmt8aGFsbHxkaXNhYmlsaXR5fHN0dWRpb3xjZW50ZXJ8Y291cnR8cGxhbnR8Z3ltfGxhYnxjbGVhbmluZ3xzdG9yYWdlfHByb3BlcnR5JyldIDwtICdQdWJsaWMgCicKbGlzdF9wcm9wZXJ0eVtzdHJfZGV0ZWN0KHRvbG93ZXIobGlzdF9wcm9wZXJ0eSksICd1bmRldGVybWluZWR8bm9uZScpXSA8LSAnVW5kZWZpbmVkJwpgYGAKCgpgYGB7ciBjb21iaW5lfQojIGNvbWJpbmUgcmVjb2RlZCBwcm9wZXJ0eSBjYXRlZ29yaWVzIHdpdGggb3JpZ2luYWwgcHJvcGVydHlfdXNlX2Rlc2MgY29sdW1ucwpkZl9jb21iaW5lIDwtIGNiaW5kKHVuaXF1ZShkZl9oaWdoZXN0JFBST1BFUlRZX1VTRV9ERVNDKSwgbGlzdF9wcm9wZXJ0eSkKY29sbmFtZXMoZGZfY29tYmluZSkgPC0gYygnUFJPUEVSVFlfVVNFX0RFU0MnLCAncHJvcGVydHknKQpkZl9jb21iaW5lIDwtIGFzLmRhdGEuZnJhbWUoZGZfY29tYmluZSkKYGBgCgpgYGB7ciBqb2luIHByb3BlcnR5LCB3YXJuaW5nPUZBTFNFfQojIGpvaW4gdGhlIHJlY29kZWQgcHJvcGVydHkgY29sdW1uIGJhY2sgdG8gdGhlIGRhdGFmcmFtZQpkZl9maXJlX3Byb3BlcnR5IDwtIGxlZnRfam9pbihkZl9maXJlLCBkZl9jb21iaW5lLCBieSA9ICdQUk9QRVJUWV9VU0VfREVTQycpCgojIHJhbmsgdGhlICJwcm9wZXJ0eSIgdmFyaWFibGUncyBsZXZlbCBieSB0aGUgbnVtYmVyIG9mIGluY2lkZW50cyBmYWxsaW5nIGludG8gdGhlc2UgY2F0ZWdvcmllcwpyYW5rZWQgPC0gc29ydCh0YWJsZShkZl9maXJlX3Byb3BlcnR5JHByb3BlcnR5KSwgZGVjcmVhc2luZyA9IFQpCmRmX2ZpcmVfcHJvcGVydHkkcHJvcGVydHkgPC0gZmFjdG9yKGRmX2ZpcmVfcHJvcGVydHkkcHJvcGVydHksIGxldmVscyA9IG5hbWVzKHJhbmtlZCkpCiMjIGFib3ZlIDIgbGluZXMgb2YgY29kZSBhcmUgdHJ5aW5nIHRvIHJhbmtpbmcgdHlwZXMgb2YgcHJvcGVydHkgYnkgdGhlaXIgZnJlcXVlbmN5LCBhbmQgdXNlIHRoaXMgdG8gc2hvdyBhIG1vcmUgaW1mb3JtYXRpdmUgbGVnZW5kIGluIHRoZSBmb2xsb3dpbmcgbWFwLiAKCmBgYAoKCmBgYHtyIFEyYSwgY2FjaGU9RkFMU0UsIGZpZy5hbGlnbj0iY2VudGVyIn0KIyBicmV3IHRoZSBjb2xvcnMgZm9yIHRoZSBwcm9wZXJ0eSB2YXJpYWJsZQpjb2xvcnMgPC0gYnJld2VyLnBhbCh1bmlxdWVOKGRmX2ZpcmVfcHJvcGVydHkkcHJvcGVydHkpLCAiU2V0MiIpCnByb3BDb2wgPC0gY29sb3JGYWN0b3IoY29sb3JzLCBkZl9maXJlX3Byb3BlcnR5JHByb3BlcnR5KQoKIyBwaWNrIG91dCBkYXRhIHdpdGggaGlnaGVzdCBsZXZlbCBvZiBhbGFybQpkZl9oaWdoZXN0X3Byb3BlcnR5IDwtIHN1YnNldChkZl9maXJlX3Byb3BlcnR5LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGZfZmlyZV9wcm9wZXJ0eSRISUdIRVNUX0xFVkVMX0RFU0MgPT0gJzcgLSBTaWduYWwgNy01JykKCgpiYXNlX21hcCAlPiUKICBhZGRDaXJjbGVzKGRhdGEgPSBkZl9oaWdoZXN0X3Byb3BlcnR5LAogICAgICAgICAgICAgbG5nID0gfmxvbiwgbGF0ID0gfmxhdCwgcmFkaXVzID0gMSwgY29sb3IgPSB+cHJvcENvbChwcm9wZXJ0eSksCiAgICAgICAgICAgICB3ZWlnaHQgPSAxLCBzdHJva2UgPSAxLCBmaWxsT3BhY2l0eSA9IC43LCAKICAgICAgICAgICAgIHBvcHVwID0gcGFzdGUwKCdBZGRyZXNzOiAnLCBkZl9oaWdoZXN0X3Byb3BlcnR5JGFkZHJlc3MsICc8YnIvPicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0luY2lkZW50IERhdGE6ICcsIGRmX2hpZ2hlc3RfcHJvcGVydHkkSU5DSURFTlRfREFURV9USU1FLCAnPGJyLz4nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1RvdGFsIEluY2lkZW50IER1cmF0aW9uOiAnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoZGZfaGlnaGVzdF9wcm9wZXJ0eSRUT1RBTF9JTkNJREVOVF9EVVJBVElPTi82MCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnIG1pbnV0ZXM8YnIvPicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnUHJvcGVydHlUeXBlOiAnLCBkZl9oaWdoZXN0X3Byb3BlcnR5JHByb3BlcnR5KSkgJT4lCiAgYWRkTGVnZW5kKGRhdGEgPSBkZl9oaWdoZXN0X3Byb3BlcnR5LCBncm91cCA9ICdJbmNpZGVudHMnLAogICAgICAgICAgICB0aXRsZSA9ICJQcm9wZXJ0eSBUeXBlcyIsIHBvc2l0aW9uID0gInRvcGxlZnQiLAogICAgICAgICAgICBwYWwgPSBwcm9wQ29sLCB2YWx1ZXMgPSB+cHJvcGVydHkpCmBgYAoKCgojIyMgYikgQ2x1c3RlcgoKYGBge3IgUTJiLCBjYWNoZT1GQUxTRSwgZmlnLmFsaWduPSJjZW50ZXIifQpiYXNlX21hcCAlPiUKICBhZGRDaXJjbGVNYXJrZXJzKGRhdGEgPSBkZl9oaWdoZXN0X3Byb3BlcnR5LAogICAgICAgICAgICAgbG5nID0gfmxvbiwgbGF0ID0gfmxhdCwgcmFkaXVzID0gLjEsIGNvbG9yID0gfnByb3BDb2wocHJvcGVydHkpLAogICAgICAgICAgICAgc3Ryb2tlID0gMCwgZmlsbE9wYWNpdHkgPSAuOSwgCiAgICAgICAgICAgICBwb3B1cCA9IHBhc3RlMCgnQWRkcmVzczogJywgZGZfaGlnaGVzdF9wcm9wZXJ0eSRhZGRyZXNzLCAnPGJyLz4nLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdJbmNpZGVudCBEYXRhOiAnLCBkZl9oaWdoZXN0X3Byb3BlcnR5JElOQ0lERU5UX0RBVEVfVElNRSwgJzxici8+JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdUb3RhbCBJbmNpZGVudCBEdXJhdGlvbjogJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKGRmX2hpZ2hlc3RfcHJvcGVydHkkVE9UQUxfSU5DSURFTlRfRFVSQVRJT04vNjApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJyBtaW51dGVzPGJyLz4nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1Byb3BlcnR5VHlwZTogJywgZGZfaGlnaGVzdF9wcm9wZXJ0eSRwcm9wZXJ0eSksIAogICAgICAgICAgICAgY2x1c3Rlck9wdGlvbnMgPSBtYXJrZXJDbHVzdGVyT3B0aW9ucyhzcGlkZXJmeU9uTWF4Wm9vbSA9IDEwKSkgJT4lCiAgYWRkTGVnZW5kKGRhdGEgPSBkZl9oaWdoZXN0X3Byb3BlcnR5LCBncm91cCA9ICdJbmNpZGVudHMnLAogICAgICAgICAgICB0aXRsZSA9ICJQcm9wZXJ0eSBUeXBlcyIsIHBvc2l0aW9uID0gInRvcGxlZnQiLAogICAgICAgICAgICBwYWwgPSBwcm9wQ29sLCB2YWx1ZXMgPSB+cHJvcGVydHkpCmBgYAoKIyMgMy4gRmlyZSBIb3VzZXMKCmBgYHtyIGFkZCBpY29ufQojIGFkZCBvbiBhbiBpY29uIHBuZwpob3VzZV9pY29uIDwtIGljb25zKGljb25VcmwgPSAKICAiL1VzZXJzL2dyZWF0eWlmYW4vRGVza3RvcC9AQ29sdW1iaWEvMjAyMHNwcmluZy8yX0RhdGFWaXovYXNzaWdubWVudC9hc3NpZ25tZW50Mi9ob3VzZS1pY29uLnBuZyIsCiAgICAgICAgICAgICAgICAgICAgaWNvbldpZHRoID0gOCwgaWNvbkhlaWdodCA9IDgpCmBgYAoKCmBgYHtyIFEzLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1GQUxTRSwgZmlnLmFsaWduPSJjZW50ZXIifQpiYXNlX21hcCAlPiUKICBhZGRDaXJjbGVNYXJrZXJzKGRhdGEgPSBkZl9oaWdoZXN0X3Byb3BlcnR5LCBncm91cCA9ICdJbmNpZGVudHMnLCAKICAgICAgICAgICAgICAgICAgIGxuZyA9IH5sb24sIGxhdCA9IH5sYXQsIAogICAgICAgICAgICAgICAgICAgcmFkaXVzID0gZGZfaGlnaGVzdF9wcm9wZXJ0eSRVTklUU19PTlNDRU5FLzQsCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IH5wcm9wQ29sKHByb3BlcnR5KSwKICAgICAgICAgICAgICAgICAgIHN0cm9rZSA9IDAsIHdlaWdodCA9IDAsIGZpbGxPcGFjaXR5ID0gLjcsIAogICAgICAgICAgICAgICAgICAgcG9wdXAgPSB+cGFzdGUwKCdBZGRyZXNzOiAnLCBkZl9oaWdoZXN0X3Byb3BlcnR5JGFkZHJlc3MsICc8YnIvPicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0luY2lkZW50IERhdGE6ICcsIGRmX2hpZ2hlc3RfcHJvcGVydHkkSU5DSURFTlRfREFURV9USU1FLCAnPGJyLz4nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1RvdGFsIEluY2lkZW50IER1cmF0aW9uOiAnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoZGZfaGlnaGVzdF9wcm9wZXJ0eSRUT1RBTF9JTkNJREVOVF9EVVJBVElPTi82MCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJyBtaW51dGVzPGJyLz4nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1Byb3BlcnR5VHlwZTogJywgZGZfaGlnaGVzdF9wcm9wZXJ0eSRwcm9wZXJ0eSkpICU+JQogIGFkZExlZ2VuZChkYXRhID0gZGZfaGlnaGVzdF9wcm9wZXJ0eSwgZ3JvdXAgPSAnSW5jaWRlbnRzJywKICAgICAgICAgICAgcGFsID0gcHJvcENvbCwgdmFsdWVzID0gfnByb3BlcnR5LCAKICAgICAgICAgICAgdGl0bGUgPSAnUHJvcGVydHkgVHlwZXMnLCBwb3NpdGlvbiA9ICd0b3BsZWZ0JykgJT4lCiAgYWRkTWFya2VycyhkYXRhID0gZGZfaG91c2UsIGdyb3VwID0gJ0ZpcmVob3VzZXMnLCAKICAgICAgICAgICAgIGxuZyA9IH5Mb25naXR1ZGUsIGxhdCA9IH5MYXRpdHVkZSwKICAgICAgICAgICAgIGljb24gPSBob3VzZV9pY29uLCAKICAgICAgICAgICAgIHBvcHVwID0gfnBhc3RlMCgnQWRkcmVzczogJywgZGZfaG91c2UkRmFjaWxpdHlBZGRyZXNzLCAnPGJyLz4nLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdCb3JvdWdoOiAnLCBkZl9ob3VzZSRCb3JvdWdoKSkgJT4lCiAgYWRkTGF5ZXJzQ29udHJvbChiYXNlR3JvdXBzID0gJ29wZW5TdHJlZXROWUMnLAogICAgICAgICAgICAgICAgICAgb3ZlcmxheUdyb3VwcyA9IGMoJ0luY2lkZW50cycsJ0ZpcmVob3VzZXMnKSwgCiAgICAgICAgICAgICAgICAgICBvcHRpb25zID0gbGF5ZXJzQ29udHJvbE9wdGlvbnMoY29sbGFwc2VkID0gVCkpCmBgYAoKIyMgNC4gRGlzdGFuY2UgZnJvbSBGaXJlaG91c2UgYW5kIFJlc3BvbnNlIFRpbWUKCiMjIyBhKSBDYWxjdWxhdGUgRGlzdGFuY2UKCmBgYHtyIGNhbGN1bGF0aW5nIGRpc3RhbmNlfQojIHRoaXMgZnVuY3Rpb24gcmV0dXJucyBucm93KHgpICogbnJvdyh5KSBtYXRyaXgsIHdoZXJlIHRoZSBpLXRoIGNvbHVtbiBpbmRpY2F0ZXMgdGhlIGRpc3RhbmNlcyBiZXR3ZWVuIGVhY2ggZ2VvcG9pbnQgaW4geCB3aXRoIHRoZSBpLXRoIHBvaW50IGluIHkuIFNpbWlsYXJseSwgZWFjaCBlbGVtZW50IGluIHRoZSBqLXRoIHJvdyBtZWFucyB0aGUgZGlzdGFuY2UgYmV0d2VlbiB0aGUgai10aCBwb2ludCBpbiB4IHdpdGggZWFjaCBwb2ludCBpbiB5LiBJbiBvdXIgY2FzZSwgaWYgd2Ugd2FudCB0byBmaW5kIG91dCB0aGUgbmVhcmVzdCBmaXJlaG91c2UgZm9yIGEgY2VydGFpbiBwb2ludCwgd2UgaGF2ZSB0byBmaW5kIHRoZSBtaW5pbXVtIGVsZW1lbnQgZm9yIGVhY2ggcm93LCBhbmQgcmV0dXJuIHRoZSBudW1iZXIgb2YgY29sdW1uIHdoZXJlIHRoZSBtaW5pbXVtIHBvaW50IGlzIGluLCB3aGljaCBpcyB0aGUgbmVhcmVzdCBmaXJlaG91c2UgZm9yIHRoYXQgaW5jaWRlbnQuIAoKbXhfZGlzdCA8LSBkaXN0bSh4ID0gbWF0cml4KGRhdGEgPSBjKGRmX2ZpcmUkbG9uLCBkZl9maXJlJGxhdCksIG5jb2wgPSAyKSwKICAgICAgeSA9IG1hdHJpeChkYXRhID0gYyhkZl9ob3VzZSRMb25naXR1ZGUsIGRmX2hvdXNlJExhdGl0dWRlKSwgbmNvbCA9IDIpLCAKICAgICAgZnVuID0gZGlzdEdlbykKCm1pbl9kaXN0IDwtIGFwcGx5KG14X2Rpc3QsIDEsIG1pbiwgbmEucm0gPSBUKQoKbmVhcmVzdF9ob3VzZSA8LSBhcHBseShteF9kaXN0LCAxLCBmdW5jdGlvbih4KXdoaWNoKHggPT0gbWluKHgsIG5hLnJtID0gVCkpKQoKIyBucm93KGRmX2hvdXNlKSAjIHdlIGhhdmUgMjE4IGZpcmUgaG91c2VzCgojIHN1bW1hcnkobmVhcmVzdF9ob3VzZSkgIyBldmVyeXRoaW5nIHNlZW1zIHJpZ2h0CgpkZl9maXJlX3Byb3BlcnR5JG1pbl9kaXN0IDwtIG1pbl9kaXN0CmRmX2ZpcmVfcHJvcGVydHkkbmVhcmVzdF9ob3VzZSA8LSBuZWFyZXN0X2hvdXNlCmBgYAoKCmBgYHtyIGdldCB0aW1lIGRpZmZlcmVuY2V9CgpkZl9maXJlX3Byb3BlcnR5JGRpZmZfdGltZSA8LSBkZl9maXJlX3Byb3BlcnR5JEFSUklWQUxfREFURV9USU1FIC0KICBkZl9maXJlX3Byb3BlcnR5JElOQ0lERU5UX0RBVEVfVElNRQoKZGZfZmlyZV9wcm9wZXJ0eSRkaWZmX3RpbWUgPC0gYXMubnVtZXJpYyhkZl9maXJlX3Byb3BlcnR5JGRpZmZfdGltZSkKCmBgYAoKCmBgYHtyIG91dGxpZXJzIGRyb3B9CiMgcmVtb3ZlIG91dGxpZXJzLCBmb3IgbW9yZSBpbmZvcm1hdGl2ZSBncmFwaHMKCiMjIGNoZWNrIGZvciB0aGUgb3V0bGllcnMKaGVhZChzb3J0KGRmX2ZpcmVfcHJvcGVydHkkbWluX2Rpc3QsIGRlY3JlYXNpbmcgID0gVCkpICMgb25lIDEwMTgxMi4xNjUgc2hvdWxkIGJlIHJlbW92ZWQKaGVhZChzb3J0KGRmX2ZpcmVfcHJvcGVydHkkZGlmZl90aW1lLCBkZWNyZWFzaW5nID0gVCkpICMgdHdvIDUzMzkgYW5kIDI2MTMgc2hvdWxkIGJlIHJlbW92ZWQKaGVhZChzb3J0KGRmX2ZpcmVfcHJvcGVydHkkZGlmZl90aW1lLCBkZWNyZWFzaW5nID0gRikpICMgb25lIG5lZ2F0aXZlIG51bWJlciBzaG91bGQgYmUgcmVtb3ZlZAoKZGZfZmlyZV9wcm9wZXJ0eSA8LSBkZl9maXJlX3Byb3BlcnR5Wy13aGljaChkZl9maXJlX3Byb3BlcnR5JG1pbl9kaXN0ID4gMTAxODEyLjE2NSksXQpkZl9maXJlX3Byb3BlcnR5IDwtIGRmX2ZpcmVfcHJvcGVydHlbLXdoaWNoKGRmX2ZpcmVfcHJvcGVydHkkZGlmZl90aW1lID4gMjYwMCksXQpkZl9maXJlX3Byb3BlcnR5IDwtIGRmX2ZpcmVfcHJvcGVydHlbLXdoaWNoKGRmX2ZpcmVfcHJvcGVydHkkZGlmZl90aW1lIDwgMCksXQpgYGAKCmBgYHtyIFE0YV8xLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1GQUxTRSwgZmlnLmFsaWduPSJjZW50ZXIifQoKZ2dwbG90KGRmX2ZpcmVfcHJvcGVydHksIGFlcyh4ID0gbWluX2Rpc3QsIHkgPSBkaWZmX3RpbWUvNjApKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IC41LCBjb2xvciA9ICdyZWQnKSArIAogIGdlb21fc21vb3RoKG1ldGhvZCA9ICdsbScsIGNvbG9yID0gJ29yYW5nZScsIGxpbmV0eXBlID0gMikgKwogIHNjYWxlX3hfbG9nMTAoKSArCiAgZ2d0aGVtZXM6OnRoZW1lX2Vjb25vbWlzdF93aGl0ZShncmF5X2JnID0gRikgKwogIHlsYWIoIiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKwogIHhsYWIoImxvZyhkaXN0YW5jZSkiKSArCiAgdGhlbWUoYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHZqdXN0ID0gLTMpKSArCiAgZ2d0aXRsZShsYWJlbCA9ICJUaW1lIEZpcmUgRmlnaHRlcnMgU3BlbnQgQmVmb3JlIFRoZW0gSW4gdGhlIFNjZW5lIChtaW51dGVzKSIpCgpgYGAKCmBgYHtyIGNvbGxhcHNpbmcgYWxhcm1zfQojIG1pbmltaXplIHRoZSBjYXRlZ29yaWVzIG9mIGFsYXJtcyBhZ2FpbgpkZl9maXJlX3Byb3BlcnR5JHJlc2NhbGUgPC0gaWZlbHNlKGRmX2ZpcmVfcHJvcGVydHkkSElHSEVTVF9MRVZFTF9ERVNDID09ICcxIC0gTW9yZSB0aGFuIGluaXRpYWwgYWxhcm0sIGxlc3MgdGhhbiBTaWduYWwgNy01JyB8IGRmX2ZpcmVfcHJvcGVydHkkSElHSEVTVF9MRVZFTF9ERVNDID09IjAgLSBJbml0aWFsIGFsYXJtIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiMSIsIGRmX2ZpcmVfcHJvcGVydHkkSElHSEVTVF9MRVZFTF9ERVNDKQoKIyBzZXQgdGhpcyB0byBmYWN0b3IKZGZfZmlyZV9wcm9wZXJ0eSRyZXNjYWxlIDwtIGZhY3RvcihkZl9maXJlX3Byb3BlcnR5JHJlc2NhbGUpCmxldmVscyhkZl9maXJlX3Byb3BlcnR5JHJlc2NhbGUpIDwtIGMoImxlc3MgdGhhbiAybmQgYWxhcm0iLCAiMm5kIGFsYXJtIiwgIjNyZCBhbGFybSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI0dGggYWxhcm0gIiwgIjV0aCBhbGFybSIsICJTaWduYWwgNy01IikKYGBgCgpgYGB7ciBRNGFfMiwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0UsIGZpZy5hbGlnbj0iY2VudGVyIn0KCmdncGxvdChzdWJzZXQoZGZfZmlyZV9wcm9wZXJ0eSwgc3Vic2V0ID0gIWRmX2ZpcmVfcHJvcGVydHkkcmVzY2FsZSAlaW4lIE5BKSwgCiAgICAgICBhZXMoeCA9IG1pbl9kaXN0LCB5ID0gZGlmZl90aW1lLzYwKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuNSwgY29sb3IgPSAncmVkJykgKyAKICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nLCBjb2xvciA9ICdvcmFuZ2UnLCBsaW5ldHlwZSA9IDIpICsKICBzY2FsZV94X2xvZzEwKCkgKwogIGdndGhlbWVzOjp0aGVtZV9lY29ub21pc3Rfd2hpdGUoZ3JheV9iZyA9IEYpICsKICB5bGFiKCIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsKICB4bGFiKCJsb2coZGlzdGFuY2UpIikgKwogIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dCh2anVzdCA9IC0zKSwKICAgICAgICB0aXRsZSA9IGVsZW1lbnRfdGV4dCh2anVzdCA9IDQpLCAKICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAiZ3JheSIpLAogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSkgKwogIGdndGl0bGUobGFiZWwgPSAiVGltZSBGaXJlIEZpZ2h0ZXJzIFNwZW50IEJlZm9yZSBUaGVtIEluIHRoZSBTY2VuZSAobWludXRlcykiKSArCiAgZmFjZXRfd3JhcCh+IHJlc2NhbGUsICkgCgogIApgYGAKCgoKIyMjIGIpIE1hcCBvZiBSZXNwb25zZSBUaW1lcwoKYGBge3IgUTRiMSwgZmlnLmFsaWduPSJjZW50ZXIifQoKcGFsMiA8LSBicmV3ZXIucGFsKHVuaXF1ZU4oZGZfZmlyZV9wcm9wZXJ0eSRwcm9wZXJ0eSksICJQYWlyZWQiKQogIApyZXNpZGVuY2UgPC0gc3Vic2V0KGRmX2ZpcmVfcHJvcGVydHksIGRmX2ZpcmVfcHJvcGVydHkkcHJvcGVydHkgPT0gIlJlc2lkZW5jZSIpCm9wZW5BIDwtICBzdWJzZXQoZGZfZmlyZV9wcm9wZXJ0eSwgZGZfZmlyZV9wcm9wZXJ0eSRwcm9wZXJ0eSA9PSAiT3BlbiAKICAgICAgICAgICAgICAgICAiKQpkb3JtIDwtIHN1YnNldChkZl9maXJlX3Byb3BlcnR5LCBkZl9maXJlX3Byb3BlcnR5JHByb3BlcnR5ID09ICJEb3JtcywgU2hlbHRlcnMsIEhvdGVscyIpCnB1YmxpYyA8LSBzdWJzZXQoZGZfZmlyZV9wcm9wZXJ0eSwgZGZfZmlyZV9wcm9wZXJ0eSRwcm9wZXJ0eSA9PSAiUHVibGljIAogICAgICAgICAgICAgICAgICIpCnNjaG9vbCA8LSBzdWJzZXQoZGZfZmlyZV9wcm9wZXJ0eSwgZGZfZmlyZV9wcm9wZXJ0eSRwcm9wZXJ0eSA9PSAiU2Nob29scyIpCm1lZGljYWwgPC0gc3Vic2V0KGRmX2ZpcmVfcHJvcGVydHksIGRmX2ZpcmVfcHJvcGVydHkkcHJvcGVydHkgPT0gIk1lZGljYWwiKQoKCmJhc2VfbWFwICU+JQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IHJlc2lkZW5jZSwgCiAgICAgICAgICAgICAgICAgICBncm91cCA9ICdSZXNpZGVuY2UnLCAKICAgICAgICAgICAgICAgICAgIGxuZyA9IH5sb24sIGxhdCA9IH5sYXQsIAogICAgICAgICAgICAgICAgICAgY29sb3IgPSBwYWwyWzFdLAogICAgICAgICAgICAgICAgICAgcmFkaXVzID0gcmVzaWRlbmNlJGRpZmZfdGltZS8xMDAsCiAgICAgICAgICAgICAgICAgICBzdHJva2UgPSAwLCB3ZWlnaHQgPSAwLCBmaWxsT3BhY2l0eSA9IC43LCAKICAgICAgICAgICAgICAgICAgIHBvcHVwID0gfnBhc3RlMCgnQWRkcmVzczogJywgcmVzaWRlbmNlJGFkZHJlc3MsICc8YnIvPicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0luY2lkZW50IERhdGE6ICcsIHJlc2lkZW5jZSRJTkNJREVOVF9EQVRFX1RJTUUsICc8YnIvPicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnVG90YWwgSW5jaWRlbnQgRHVyYXRpb246ICcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChyZXNpZGVuY2UkVE9UQUxfSU5DSURFTlRfRFVSQVRJT04vNjApLCAnIG1pbicpKSAlPiUgCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhID0gb3BlbkEsIAogICAgICAgICAgICAgICAgICAgZ3JvdXAgPSAnT3BlbiAKICAgICAgICAgICAgICAgICAgICcsIAogICAgICAgICAgICAgICAgICAgbG5nID0gfmxvbiwgbGF0ID0gfmxhdCwgCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IHBhbDJbMl0sCiAgICAgICAgICAgICAgICAgICByYWRpdXMgPSBvcGVuQSRkaWZmX3RpbWUvMTAwLAogICAgICAgICAgICAgICAgICAgc3Ryb2tlID0gMCwgd2VpZ2h0ID0gMCwgZmlsbE9wYWNpdHkgPSAuNywgCiAgICAgICAgICAgICAgICAgICBwb3B1cCA9IH5wYXN0ZTAoJ0FkZHJlc3M6ICcsIG9wZW5BJGFkZHJlc3MsICc8YnIvPicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0luY2lkZW50IERhdGE6ICcsIG9wZW5BJElOQ0lERU5UX0RBVEVfVElNRSwgJzxici8+JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdUb3RhbCBJbmNpZGVudCBEdXJhdGlvbjogJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKG9wZW5BJFRPVEFMX0lOQ0lERU5UX0RVUkFUSU9OLzYwKSwgJyBtaW4nKSkgJT4lIAogICAgYWRkQ2lyY2xlTWFya2VycyhkYXRhID0gZG9ybSwgCiAgICAgICAgICAgICAgICAgICBncm91cCA9ICdEb3JtcywgU2hlbHRlcnMsIEhvdGVscycsIAogICAgICAgICAgICAgICAgICAgbG5nID0gfmxvbiwgbGF0ID0gfmxhdCwgCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IHBhbDJbM10sCiAgICAgICAgICAgICAgICAgICByYWRpdXMgPSBkb3JtJGRpZmZfdGltZS8xMDAsCiAgICAgICAgICAgICAgICAgICBzdHJva2UgPSAwLCB3ZWlnaHQgPSAwLCBmaWxsT3BhY2l0eSA9IC43LCAKICAgICAgICAgICAgICAgICAgIHBvcHVwID0gfnBhc3RlMCgnQWRkcmVzczogJywgZG9ybSRhZGRyZXNzLCAnPGJyLz4nLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdJbmNpZGVudCBEYXRhOiAnLCBkb3JtJElOQ0lERU5UX0RBVEVfVElNRSwgJzxici8+JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdUb3RhbCBJbmNpZGVudCBEdXJhdGlvbjogJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKGRvcm0kVE9UQUxfSU5DSURFTlRfRFVSQVRJT04vNjApLCAnIG1pbicpKSAlPiUgCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhID0gcHVibGljLCAKICAgICAgICAgICAgICAgICAgIGdyb3VwID0gJ1B1YmxpYyAKICAgICAgICAgICAgICAgICAgICcsIAogICAgICAgICAgICAgICAgICAgbG5nID0gfmxvbiwgbGF0ID0gfmxhdCwgCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IHBhbDJbNF0sCiAgICAgICAgICAgICAgICAgICByYWRpdXMgPSBwdWJsaWMkZGlmZl90aW1lLzEwMCwKICAgICAgICAgICAgICAgICAgIHN0cm9rZSA9IDAsIHdlaWdodCA9IDAsIGZpbGxPcGFjaXR5ID0gLjcsIAogICAgICAgICAgICAgICAgICAgcG9wdXAgPSB+cGFzdGUwKCdBZGRyZXNzOiAnLCBwdWJsaWMkYWRkcmVzcywgJzxici8+JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnSW5jaWRlbnQgRGF0YTogJywgcHVibGljJElOQ0lERU5UX0RBVEVfVElNRSwgJzxici8+JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdUb3RhbCBJbmNpZGVudCBEdXJhdGlvbjogJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKHB1YmxpYyRUT1RBTF9JTkNJREVOVF9EVVJBVElPTi82MCksICcgbWluJykpICU+JSAKICBhZGRDaXJjbGVNYXJrZXJzKGRhdGEgPSBzY2hvb2wsIAogICAgICAgICAgICAgICAgICAgZ3JvdXAgPSAnU2Nob29scycsIAogICAgICAgICAgICAgICAgICAgbG5nID0gfmxvbiwgbGF0ID0gfmxhdCwgCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IHBhbDJbNV0sCiAgICAgICAgICAgICAgICAgICByYWRpdXMgPSBzY2hvb2wkZGlmZl90aW1lLzEwMCwKICAgICAgICAgICAgICAgICAgIHN0cm9rZSA9IDAsIHdlaWdodCA9IDAsIGZpbGxPcGFjaXR5ID0gLjcsIAogICAgICAgICAgICAgICAgICAgcG9wdXAgPSB+cGFzdGUwKCdBZGRyZXNzOiAnLCBzY2hvb2wkYWRkcmVzcywgJzxici8+JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnSW5jaWRlbnQgRGF0YTogJywgc2Nob29sJElOQ0lERU5UX0RBVEVfVElNRSwgJzxici8+JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdUb3RhbCBJbmNpZGVudCBEdXJhdGlvbjogJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKHNjaG9vbCRUT1RBTF9JTkNJREVOVF9EVVJBVElPTi82MCksICcgbWluJykpICU+JSAKICBhZGRDaXJjbGVNYXJrZXJzKGRhdGEgPSBtZWRpY2FsLCAKICAgICAgICAgICAgICAgICAgIGdyb3VwID0gJ01lZGljYWwnLCAKICAgICAgICAgICAgICAgICAgIGxuZyA9IH5sb24sIGxhdCA9IH5sYXQsIAogICAgICAgICAgICAgICAgICAgY29sb3IgPSBwYWwyWzZdLAogICAgICAgICAgICAgICAgICAgcmFkaXVzID0gbWVkaWNhbCRkaWZmX3RpbWUvMTAwLAogICAgICAgICAgICAgICAgICAgc3Ryb2tlID0gMCwgd2VpZ2h0ID0gMCwgZmlsbE9wYWNpdHkgPSAuNywgCiAgICAgICAgICAgICAgICAgICBwb3B1cCA9IH5wYXN0ZTAoJ0FkZHJlc3M6ICcsIG1lZGljYWwkYWRkcmVzcywgJzxici8+JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnSW5jaWRlbnQgRGF0YTogJywgbWVkaWNhbCRJTkNJREVOVF9EQVRFX1RJTUUsICc8YnIvPicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnVG90YWwgSW5jaWRlbnQgRHVyYXRpb246ICcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChtZWRpY2FsJFRPVEFMX0lOQ0lERU5UX0RVUkFUSU9OLzYwKSwgJyBtaW4nKSkgJT4lIAogIGFkZExheWVyc0NvbnRyb2wob3ZlcmxheUdyb3VwcyA9IGMoJ1Jlc2lkZW5jZScsJ09wZW4gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0Rvcm1zLCBTaGVsdGVycywgSG90ZWxzJywgJ1B1YmxpYyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnTWVkaWNhbCcsJ1NjaG9vbHMnKSwKICAgICAgICAgICAgICAgICAgIG9wdGlvbnMgPSBsYXllcnNDb250cm9sT3B0aW9ucyhjb2xsYXBzZWQgPSBGKSwgCiAgICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICJ0b3BsZWZ0IikgCiAgCmBgYAoKSXQgaXMgcmVhbGx5IGhhcmQgdG8gY29tcGFyZSB0aGUgcmVzcG9uc2UgdGltZSBhbW9uZyB2YXJpb3VzIHR5cGVzIG9mIHByb3BlcnR5IG9ubHkgYnkgY29sb3IgYW5kIHNpemUsIHNvIEkgdXNlIHRoZSBjaGVja2JveCBmZWF0dXJlIGluIHRoZSBsYXllcnMgY29udHJvbCB0byBtYWtlIGl0IGVhc2llciBmb3IgcmVhZGVycyB0byBjb21wYXJlIGJ5IGNoZWNraW5nIGRpZmZlcmVudCBraW5kcyBvZiBwcm9wZXJ0eS4gCgoKYGBge3IgUTRhX2hpZ2hlc3RMZXZlbF9yZXNwb25zLCBmaWcuYWxpZ249ImNlbnRlciJ9CmxldmVscyhkZl9maXJlX3Byb3BlcnR5JEhJR0hFU1RfTEVWRUxfREVTQylbMl0gPC0gIjEgLSBNb3JlIHRoYW4gaW5pdGlhbCBhbGFybSIKCnBhbDIgPC0gYnJld2VyLnBhbCh1bmlxdWVOKGRmX2ZpcmVfcHJvcGVydHlbIWRmX2ZpcmVfcHJvcGVydHkkSElHSEVTVF9MRVZFTF9ERVNDICVpbiUgTkEsIF0kSElHSEVTVF9MRVZFTF9ERVNDKSwgCiAgICAgICAgICAgICAgICAgICAnWWxPclJkJykKcHJvcENvbDIgPC0gY29sb3JGYWN0b3IocGFsZXR0ZSA9IHBhbDIsIGRvbWFpbiA9IGRmX2ZpcmVfcHJvcGVydHkkSElHSEVTVF9MRVZFTF9ERVNDKQoKYmFzZV9tYXAgJT4lCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhID0gZGZfZmlyZV9wcm9wZXJ0eVshZGZfZmlyZV9wcm9wZXJ0eSRISUdIRVNUX0xFVkVMX0RFU0MgJWluJSBOQSwgXSwgCiAgICAgICAgICAgICAgICAgICBncm91cCA9ICdJbmNpZGVudHMnLCAKICAgICAgICAgICAgICAgICAgIGxuZyA9IH5sb24sIGxhdCA9IH5sYXQsIAogICAgICAgICAgICAgICAgICAgcmFkaXVzID0gZGZfZmlyZV9wcm9wZXJ0eSRkaWZmX3RpbWUvMTAwLAogICAgICAgICAgICAgICAgICAgY29sb3IgPSB+cHJvcENvbDIoSElHSEVTVF9MRVZFTF9ERVNDKSwKICAgICAgICAgICAgICAgICAgIHN0cm9rZSA9IDAsIHdlaWdodCA9IDAsIGZpbGxPcGFjaXR5ID0gLjcsIAogICAgICAgICAgICAgICAgICAgcG9wdXAgPSB+cGFzdGUwKCdBZGRyZXNzOiAnLCBkZl9maXJlX3Byb3BlcnR5JGFkZHJlc3MsICc8YnIvPicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0luY2lkZW50IERhdGE6ICcsIGRmX2ZpcmVfcHJvcGVydHkkSU5DSURFTlRfREFURV9USU1FLCAnPGJyLz4nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1RvdGFsIEluY2lkZW50IER1cmF0aW9uOiAnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoZGZfZmlyZV9wcm9wZXJ0eSRUT1RBTF9JTkNJREVOVF9EVVJBVElPTi82MCksICcgbWluPGJyLz4nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1Byb3BlcnR5VHlwZTogJywgZGZfZmlyZV9wcm9wZXJ0eSRwcm9wZXJ0eSkpICU+JQogIGFkZExlZ2VuZChkYXRhID0gZGZfZmlyZV9wcm9wZXJ0eVshZGZfZmlyZV9wcm9wZXJ0eSRISUdIRVNUX0xFVkVMX0RFU0MgJWluJSBOQSwgXSwgCiAgICAgICAgICAgIGdyb3VwID0gJ0luY2lkZW50cycsCiAgICAgICAgICAgIHBhbCA9IHByb3BDb2wyLCB2YWx1ZXMgPSB+SElHSEVTVF9MRVZFTF9ERVNDLCAKICAgICAgICAgICAgdGl0bGUgPSAnTGV2ZWwgb2YgQWxhcm1zJywgcG9zaXRpb24gPSAndG9wbGVmdCcpCgpgYGAKCgpgYGB7ciByZWFkIGdlb2pzb24sIG1lc3NhZ2U9RkFMU0UsIH0KbnljIDwtIHJlYWRPR1IoJy9Vc2Vycy9ncmVhdHlpZmFuL0Rlc2t0b3AvQENvbHVtYmlhLzIwMjBzcHJpbmcvMl9EYXRhVml6L2NvdXJzZV9tYXRlcmlhbHMvRXhlcmNpc2VzLzA3X2ZpcmUvYm9yb3VnaF9ib3VuZGFyaWVzLmdlb2pzb24nKQpgYGAKCmBgYHtyIGFkZCBkYXRhIG9uIHBvbHlnb259CmRmX2ZpcmVfcHJvcGVydHkkeWVhciA8LSB5ZWFyKGRmX2ZpcmVfcHJvcGVydHkkQVJSSVZBTF9EQVRFX1RJTUUpCgpkZl9maXJlX3Byb3BlcnR5JGJvcm9fbmFtZSA8LSBnc3ViKCdcXGR7MX1cXHMtXFxzJywgJycsIGRmX2ZpcmVfcHJvcGVydHkkQk9ST1VHSF9ERVNDKQpkZl9maXJlX3Byb3BlcnR5JHllYXIgPC0gcGFzdGUwKCJuXyIsIGRmX2ZpcmVfcHJvcGVydHkkeWVhcikgI2F2b2lkaW5nIHZhbHVhYmxlIG5hbWVzIGlzIG51bWJlcgoKYm9yb195ZWFyX2NvdW50IDwtIGRmX2ZpcmVfcHJvcGVydHkgJT4lIAogIGdyb3VwX2J5KHllYXIsIGJvcm9fbmFtZSkgJT4lIAogIGNvdW50KCkgJT4lIAogIGRyb3BfbmEoKSAlPiUgCiAgc3ByZWFkKGtleSA9ICd5ZWFyJywgdmFsdWUgPSAnbicpCgpgYGAKCmBgYHtyIGpvaW4gZGF0YSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbnljQGRhdGEgPC0gbnljQGRhdGEgJT4lIAogIGxlZnRfam9pbihib3JvX3llYXJfY291bnQsIG9uID0gImJvcm9fbmFtZSIpCmBgYAoKYGBge3IgYm9ybyBjZW50cm9pZH0KIyBnZXQgY2VudHJvaWQgZm9yIGVhY2ggYm9yb3VnaApjZW50ZXJzIDwtIGFzLmRhdGEuZnJhbWUoZ0NlbnRyb2lkKG55YywgYnlpZCA9IFQpKQpgYGAKCmBgYHtyIHNldCBwYWwgZm9yIGZvbGxvd2luZyBtYXBzfQpwYWxfeWVhciA8LSBjb2xvckJpbigiWWxPclJkIiwgYmlucyA9IHNlcShmcm9tID0gMCwgdG8gPSA5MDAsIGJ5ID0gMTAwKSkKYGBgCgpgYGB7ciBtYXAyMDEzfQoKbWFwXzIwMTMgPC0gYmFzZV9tYXAgJT4lIAogIGFkZFBvbHlnb25zKGRhdGEgPSBueWMsIHN0cm9rZSA9IEZBTFNFLCBzbW9vdGhGYWN0b3IgPSAwLjUsCiAgICAgICAgICAgICAgd2VpZ2h0PTEsIGNvbG9yPScjMzMzMzMzJywgb3BhY2l0eT0xLAogICAgICAgICAgICAgIGZpbGxDb2xvciA9IH5wYWxfeWVhcihuXzIwMTMpLAogICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gLjgpICU+JSAKICBhZGRMZWdlbmQoZGF0YSA9IG55Y0BkYXRhLCBwYWwgPSBwYWxfeWVhciwgcG9zaXRpb24gPSAidG9wbGVmdCIsIAogICAgICAgICAgICB2YWx1ZXMgPSB+bl8yMDEzLCB0aXRsZSA9ICI8Zm9udCBzaXplPSc1Jz4yMDEzPC9mb250PiIpICU+JSAKICBhZGRMYWJlbE9ubHlNYXJrZXJzKGxuZyA9IGNlbnRlcnNbLDFdLCBsYXQgPSBjZW50ZXJzWywyXSwgCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IHBhc3RlMChueWNAZGF0YSRib3JvX25hbWUsICJcbiIsIG55Y0BkYXRhJG5fMjAxMyksCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMobm9IaWRlID0gVCwgdGV4dE9ubHkgPSBULCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAnY2VudGVyJykpCgoKYGBgCgoKYGBge3IgbWFwMjAxNH0KCm1hcF8yMDE0IDwtIGJhc2VfbWFwICU+JSAKICBhZGRQb2x5Z29ucyhkYXRhID0gbnljLCBzdHJva2UgPSBGQUxTRSwgc21vb3RoRmFjdG9yID0gMC41LAogICAgICAgICAgICAgIHdlaWdodD0xLCBjb2xvcj0nIzMzMzMzMycsIG9wYWNpdHk9MSwKICAgICAgICAgICAgICBmaWxsQ29sb3IgPSB+cGFsX3llYXIobl8yMDE0KSwKICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IC44KSAlPiUgCiAgYWRkTGVnZW5kKGRhdGEgPSBueWNAZGF0YSwgcGFsID0gcGFsX3llYXIsIHBvc2l0aW9uID0gInRvcGxlZnQiLCAKICAgICAgICAgICAgdmFsdWVzID0gfm5fMjAxNCwgdGl0bGUgPSAiPGZvbnQgc2l6ZT0nNSc+MjAxNDwvZm9udD4iKSAlPiUgCiAgYWRkTGFiZWxPbmx5TWFya2VycyhsbmcgPSBjZW50ZXJzWywxXSwgbGF0ID0gY2VudGVyc1ssMl0sIAogICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBwYXN0ZTAobnljQGRhdGEkYm9yb19uYW1lLCAiXG4iLCBueWNAZGF0YSRuXzIwMTQpLAogICAgICAgICAgICAgICAgICAgICAgbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKG5vSGlkZSA9IFQsIHRleHRPbmx5ID0gVCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlyZWN0aW9uID0gJ2NlbnRlcicpKQoKYGBgCgpgYGB7ciBtYXAyMDE1fQoKbWFwXzIwMTUgPC0gYmFzZV9tYXAgJT4lIAogIGFkZFBvbHlnb25zKGRhdGEgPSBueWMsIHN0cm9rZSA9IEZBTFNFLCBzbW9vdGhGYWN0b3IgPSAwLjUsCiAgICAgICAgICAgICAgd2VpZ2h0PTEsIGNvbG9yPScjMzMzMzMzJywgb3BhY2l0eT0xLAogICAgICAgICAgICAgIGZpbGxDb2xvciA9IH5wYWxfeWVhcihuXzIwMTUpLAogICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gLjgpICU+JSAKICBhZGRMZWdlbmQoZGF0YSA9IG55Y0BkYXRhLCBwYWwgPSBwYWxfeWVhciwgcG9zaXRpb24gPSAidG9wbGVmdCIsIAogICAgICAgICAgICB2YWx1ZXMgPSB+bl8yMDE1LCB0aXRsZSA9ICI8Zm9udCBzaXplPSc1Jz4yMDE1PC9mb250PiIpICU+JSAKICBhZGRMYWJlbE9ubHlNYXJrZXJzKGxuZyA9IGNlbnRlcnNbLDFdLCBsYXQgPSBjZW50ZXJzWywyXSwgCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IHBhc3RlMChueWNAZGF0YSRib3JvX25hbWUsICJcbiIsIG55Y0BkYXRhJG5fMjAxNSksCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMobm9IaWRlID0gVCwgdGV4dE9ubHkgPSBULCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAnY2VudGVyJykpCgpgYGAKCmBgYHtyIG1hcDIwMTZ9CgptYXBfMjAxNiA8LSBiYXNlX21hcCAlPiUgCiAgYWRkUG9seWdvbnMoZGF0YSA9IG55Yywgc3Ryb2tlID0gRkFMU0UsIHNtb290aEZhY3RvciA9IDAuNSwKICAgICAgICAgICAgICB3ZWlnaHQ9MSwgY29sb3I9JyMzMzMzMzMnLCBvcGFjaXR5PTEsCiAgICAgICAgICAgICAgZmlsbENvbG9yID0gfnBhbF95ZWFyKG5fMjAxNiksCiAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAuOCkgJT4lIAogIGFkZExlZ2VuZChkYXRhID0gbnljQGRhdGEsIHBhbCA9IHBhbF95ZWFyLCBwb3NpdGlvbiA9ICJ0b3BsZWZ0IiwgCiAgICAgICAgICAgIHZhbHVlcyA9IH5uXzIwMTYsIHRpdGxlID0gIjxmb250IHNpemU9JzUnPjIwMTY8L2ZvbnQ+IikgJT4lIAogIGFkZExhYmVsT25seU1hcmtlcnMobG5nID0gY2VudGVyc1ssMV0sIGxhdCA9IGNlbnRlcnNbLDJdLCAKICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gcGFzdGUwKG55Y0BkYXRhJGJvcm9fbmFtZSwgIlxuIiwgbnljQGRhdGEkbl8yMDE2KSwKICAgICAgICAgICAgICAgICAgICAgIGxhYmVsT3B0aW9ucyA9IGxhYmVsT3B0aW9ucyhub0hpZGUgPSBULCB0ZXh0T25seSA9IFQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpcmVjdGlvbiA9ICdjZW50ZXInKSkKCmBgYAoKYGBge3IgbWFwMjAxN30KCm1hcF8yMDE3IDwtIGJhc2VfbWFwICU+JSAKICBhZGRQb2x5Z29ucyhkYXRhID0gbnljLCBzdHJva2UgPSBGQUxTRSwgc21vb3RoRmFjdG9yID0gMC41LAogICAgICAgICAgICAgIHdlaWdodD0xLCBjb2xvcj0nIzMzMzMzMycsIG9wYWNpdHk9MSwKICAgICAgICAgICAgICBmaWxsQ29sb3IgPSB+cGFsX3llYXIobl8yMDE3KSwKICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IC44KSAlPiUgCiAgYWRkTGVnZW5kKGRhdGEgPSBueWNAZGF0YSwgcGFsID0gcGFsX3llYXIsIHBvc2l0aW9uID0gInRvcGxlZnQiLCAKICAgICAgICAgICAgdmFsdWVzID0gfm5fMjAxNywgdGl0bGUgPSAiPGZvbnQgc2l6ZT0nNSc+MjAxNzwvZm9udD4iKSAlPiUgCiAgYWRkTGFiZWxPbmx5TWFya2VycyhsbmcgPSBjZW50ZXJzWywxXSwgbGF0ID0gY2VudGVyc1ssMl0sIAogICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBwYXN0ZTAobnljQGRhdGEkYm9yb19uYW1lLCAiXG4iLCBueWNAZGF0YSRuXzIwMTcpLAogICAgICAgICAgICAgICAgICAgICAgbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKG5vSGlkZSA9IFQsIHRleHRPbmx5ID0gVCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlyZWN0aW9uID0gJ2NlbnRlcicpKQoKYGBgCgpgYGB7ciBtYXAyMDE4fQoKbWFwXzIwMTggPC0gYmFzZV9tYXAgJT4lIAogIGFkZFBvbHlnb25zKGRhdGEgPSBueWMsIHN0cm9rZSA9IEZBTFNFLCBzbW9vdGhGYWN0b3IgPSAwLjUsCiAgICAgICAgICAgICAgd2VpZ2h0PTEsIGNvbG9yPScjMzMzMzMzJywgb3BhY2l0eT0xLAogICAgICAgICAgICAgIGZpbGxDb2xvciA9IH5wYWxfeWVhcihuXzIwMTgpLAogICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gLjgpICU+JSAKICBhZGRMZWdlbmQoZGF0YSA9IG55Y0BkYXRhLCBwYWwgPSBwYWxfeWVhciwgcG9zaXRpb24gPSAidG9wbGVmdCIsIAogICAgICAgICAgICB2YWx1ZXMgPSB+bl8yMDE4LCB0aXRsZSA9ICI8Zm9udCBzaXplPSc1Jz4yMDE4PC9mb250PiIpICU+JSAKICBhZGRMYWJlbE9ubHlNYXJrZXJzKGxuZyA9IGNlbnRlcnNbLDFdLCBsYXQgPSBjZW50ZXJzWywyXSwgCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IHBhc3RlMChueWNAZGF0YSRib3JvX25hbWUsICJcbiIsIG55Y0BkYXRhJG5fMjAxOCksCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMobm9IaWRlID0gVCwgdGV4dE9ubHkgPSBULCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAnY2VudGVyJykpCgpgYGAKCmBgYHtyIFE0X2IzLCBldmFsPUZBTFNFfQpsZWFmbGV0X2dyaWQgPC0gCiAgdGFnTGlzdCgKICAgIHRhZ3MkdGFibGUod2lkdGggPSAiMTAwJSIsCiAgICAgICAgICAgICAgIHRhZ3MkdHIoCiAgICAgICAgICAgICAgICAgdGFncyR0ZChtYXBfMjAxMyksCiAgICAgICAgICAgICAgICAgdGFncyR0ZChtYXBfMjAxNCkKICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgdGFncyR0cigKICAgICAgICAgICAgICAgICB0YWdzJHRkKG1hcF8yMDE1KSwKICAgICAgICAgICAgICAgICB0YWdzJHRkKG1hcF8yMDE2KQogICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICB0YWdzJHRyKAogICAgICAgICAgICAgICAgIHRhZ3MkdGQobWFwXzIwMTcpLAogICAgICAgICAgICAgICAgIHRhZ3MkdGQobWFwXzIwMTgpCiAgICAgICAgICAgICAgICkKICAgICkKICApCgpicm93c2FibGUobGVhZmxldF9ncmlkKQpgYGAKCgoKCgoK